package com.zy.explore.db.infrastructure.plugins.multidbsupport.sqlchange.function;

import com.zy.explore.db.infrastructure.plugins.multidbsupport.sqlchange.Sqlchange;
import com.zy.explore.db.infrastructure.plugins.multidbsupport.sqlchange.base.SqlOper;
import org.apache.commons.lang3.StringUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


/**
 * 建类说明：sql函数工具类
 *
 * @author 中国印
 * @date 2022年02月16日 23:13:39
 */
public class SqlFunctionUtils {

    /**
     * 函数正则表达式
     */
    private static final String FUNCTION_REG = "(\\b[a-zA-z]\\w+\\b)\\s*\\(";

    /**
     * 属性说明： 匹配函数正则表达式
     * <p>
     * "[\\s\\(\\)\\'\\\"\\,]+"+
     *
     * @see Pattern
     */
    private static final Pattern FUNCTION_PATTERN = Pattern.compile(FUNCTION_REG);

    /**
     * 查找函数
     */
    public static List<SqlOper> find(final String sql, AbstractFunChangeFactory changeFactory) {
        if (StringUtils.isEmpty(sql)) {
            return new ArrayList<>();
        }
        List<SqlOper> sqlOperList = new ArrayList<>();

        List<SqlFunction> funList = findFun(sql, changeFactory);
        for (SqlFunction fun : funList) {
            FunChange fc = changeFactory.get(fun.getName());

            boolean argChangeFlag = findAndChangeFunArgs(fun, changeFactory);

            String str = null;
            //整个函数有变动
            if (fc != null) {
                str = fc.change(fun);
            }
            //参数值有变动，强制修改函数
            if (str == null && argChangeFlag) {
                str = AbstractFunChangeFactory.merge(fun.getName(), fun.getArgList());
            }
            if (str != null) {
                sqlOperList.add(SqlOper.newReplace(fun.getStart(), fun.getEnd(), str));
            }
        }
        return sqlOperList;
    }

    /**
     * 查找函数
     */
    private static List<SqlFunction> findFun(final String sql, AbstractFunChangeFactory changeFactory) {
        List<SqlFunction> funList = new ArrayList<>();

        /* 上一个参数结束位置 */
        int preAe = -1;
        Matcher matcher = FUNCTION_PATTERN.matcher(sql);

        while (matcher.find()) {
            String funName = matcher.group(1);
            funName = funName.toLowerCase();

            if (isSqlKeyword(funName)) {
                //名称为sql中关键字：非法名称，忽略
                continue;
            }
            FunChange fc = changeFactory.get(funName);
            if (fc == null) {
                //没有对应的转换方法，不需要替换
                //如果测试可以去掉此控制
                continue;
            }

            //函数开始位置
            int fs = matcher.start(1);
            //参数开始位置：相当于左括号位置
            int as = matcher.end();

            if (as < preAe) {
                //名称小于上一函数结束：函数内的函数，已处理，忽略
                continue;
            }
            //参数结束位置
            int ae = -1;

            //参数
            List<String> argList = new ArrayList<>();
            for (int i = as; i < sql.length(); ) {
                char c = sql.charAt(i);
                //空白
                if (c == ' ' || c == '\n' || c == '\r' || c == '\t') {
                    i++;
                    ae = i;
                    //忽略空白
                    continue;
                }
                if (c == ',') {
                    //忽略参数分割符
                    i++;
                    ae = i;
                    continue;
                }
                if (c == ')') {
                    //右括号结束
                    ae = i;
                    break;
                }
                //单引号开始表示字符串，最后已单引号结束
                if (c == '\'') {
                    ae = getNextChar(sql, '\'', i + 1);
                    if (ae == -1) {
                        break;
                    }
                    //结束的单引号也在值中间
                    argList.add(sql.substring(i, ae + 1));

                    int dh = getNextChar(sql, ',', ae + 1);
                    int ykh = getNextChar(sql, ')', ae + 1);
                    if (ykh == -1) {
                        //后面没有右括号，不符合sql格式
                        ae = -1;
                    } else if (dh != -1 && dh < ykh) {
                        ae = dh;
                    } else {
                        ae = ykh;
                    }

                } else {
                    //左括号、逗号、右括号位置
                    int zkh = getNextChar(sql, '(', i + 1);
                    int dh = getNextChar(sql, ',', i + 1);
                    int ykh = getNextChar(sql, ')', i + 1);
                    if (ykh == -1) {
                        //后面没有右括号，不符合sql格式
                        ae = -1;
                    } else if (zkh != -1 && dh != -1 && zkh < dh && zkh < ykh) {
                        //存在左括号、逗号，并且左括号在前面：参数中包含左括号
                        int khjs = getBracesBlockLast(sql, zkh + 1);
                        dh = getNextChar(sql, ',', khjs + 1);
                        ykh = getNextChar(sql, ')', khjs + 1);
                        if (dh != -1 && dh < ykh) {
                            ae = dh;
                            argList.add(sql.substring(i, dh));
                        } else {
                            argList.add(sql.substring(i, ykh));
                            ae = ykh;
                        }

                    } else if (zkh != -1 && zkh < ykh) {
                        int khjs = getBracesBlockLast(sql, zkh + 1);
                        ykh = getNextChar(sql, ')', khjs + 1);
                        argList.add(sql.substring(i, ykh));
                        ae = ykh;

                    } else if (dh != -1 && dh < ykh) {
                        //后面存在逗号在右括号前面
                        ae = dh;
                        argList.add(sql.substring(i, dh));
                    } else {
                        if (i < ykh) {
                            argList.add(sql.substring(i, ykh));
                        }
                        //右面右括号
                        ae = ykh;
                        break;
                    }
                    if (ae == -1) {
                        break;
                    }
                }
                i = ae;
            }
            //解析sql出错
            if (ae == -1) {
                System.out.println("解析sql出错");
                break;
            }
            SqlFunction fun = new SqlFunction(funName, argList, fs, ae + 1);
            funList.add(fun);
            preAe = fun.getEnd();
        }
        return funList;
    }

    /**
     * 查找并修改参数中的函数，返回true表示对参数有修改
     */
    private static boolean findAndChangeFunArgs(SqlFunction fun, AbstractFunChangeFactory changeFactory) {
        boolean flag = false;
        List<String> argList = fun.getArgList();
        for (int i = 0; i < argList.size(); i++) {
            String arg = argList.get(i);
            if (arg.indexOf('(') <= 0) {
                //参数存在左括号并且不是最开始为括号
                //此种格式有可能包含方法
                continue;
            }
            //查找参数中是否包含函数
            List<SqlFunction> funList = findFun(arg, changeFactory);
            if (funList.isEmpty()) {
                //不包含函数
                continue;
            }
            for (SqlFunction f : funList) {
                boolean tmp = findAndChangeFunArgs(f, changeFactory);
                if (tmp) {
                    flag = true;
                }
            }
            List<SqlOper> sqlOperList = new ArrayList<>();
            for (SqlFunction f : funList) {
                FunChange fc = changeFactory.get(f.getName());
                String str = fc.change(f);
                if (str != null) {
                    flag = true;
                    sqlOperList.add(SqlOper.newReplace(f.getStart(), f.getEnd(), str));
                }
            }
            arg = Sqlchange.change(arg, sqlOperList);
            argList.set(i, arg);
        }
        return flag;
    }


    /**
     * 判断是否为关键字
     */
    private static boolean isSqlKeyword(String key) {
        switch (key) {
            case "select":
            case "from":
            case "where":
            case "by":
            case "union":
            case "all":
            case "and":
            case "or":
            case "top":
            case "limit":
                return true;
            default:
                return false;
        }
    }

    /**
     * 查找下一个字符串位置
     */
    private static int getNextChar(String str, char findChar, int startIdx) {
        for (int i = startIdx; i < str.length(); i++) {
            if (str.charAt(i) == findChar) {
                return i;
            }
        }
        return -1;
    }

    /**
     * 获取括号块结束位置
     */
    private static int getBracesBlockLast(String str, int startIdx) {
        //层级
        int layer = 1;
        for (int i = startIdx; i < str.length(); i++) {
            char c = str.charAt(i);

            if (c == ')') {
                layer--;
                if (layer == 0) {
                    return i;
                }
            } else if (c == '(') {
                layer++;
            }
        }
        return -1;
    }
}
